/*
 * Windows Desktop          OpenAL
 * MacOS                    OpenAL
 * Linux                    OpenAL
 * Windows Phone 7.5/8.0    Xna
 * Andoid                   OpenAL
 * iOS                      ???
 */

using System;
using Microsoft.Xna.Framework.Audio;
using OpenTK;
using OpenTK.Audio.OpenAL;
using System.Threading;
using SoundEngine3.AudioIO;
using SoundEngine3.AudioProcessing;
using SoundEngine3;
using SoundEngine3.DataTypes;
using SoundEngine3.DspPlugins.Meta;
using System.Collections.Generic;
using SoundEngine3.Performance;


#if !WindowsPhone
namespace Microsoft.Xna.Framework.Audio
{
    public enum SoundState
    {
        Stopped,
        Playing,
        Paused

    }

    public enum AudioChannels
    {
        Mono,
        Stereo

    }

    public sealed class DynamicSoundEffectInstance : IDisposable
    {
        private const int BUFFERCOUNT = 2;

        private SoundState soundState = SoundState.Stopped;
        private AudioChannels channels;
        private int sampleRate;
        private ALFormat format;
        private bool looped = false;
        private float volume = 1.0f;
        private float pan = 0;
        private float pitch = 0f;
        private int sourceId;
        private int[] bufferIds;
        private int[] bufferIdsToFill;
        private int currentBufferToFill;
        private bool isDisposed = false;
        private bool hasSourceId = false;
        private Thread bufferFillerThread = null;
        private bool mRunning;
        private bool mThreadStarted;

        // Events
        public event Action BufferNeeded;

        internal void OnBufferNeeded()
        {
            if (BufferNeeded != null)
            {
                BufferNeeded();
            }
        }

        public DynamicSoundEffectInstance(int sampleRate, AudioChannels channels)
        {
            this.sampleRate = sampleRate;
            this.channels = channels;
            switch (channels)
            {
                case AudioChannels.Mono:
                    this.format = ALFormat.Mono16;
                    break;
                case AudioChannels.Stereo:
                    this.format = ALFormat.Stereo16;
                    break;
                default:
                    break;
            }
        }

        public bool IsDisposed
        {
            get
            {
                return isDisposed;
            }
        }

        public float Pan
        {
            get
            {
                return pan;
            }

            set
            {
                pan = value;
                if (hasSourceId)
                {
                    // Listener
                    // Pan
                    AL.Source(sourceId, ALSource3f.Position, pan, 0.0f, 0.1f);
                }
            }
        }

        public float Pitch
        {
            get
            {
                return pitch;
            }
            set
            {
                pitch = value;
                if (hasSourceId)
                {
                    // Pitch
                    AL.Source(sourceId, ALSourcef.Pitch, XnaPitchToAlPitch(pitch));
                }
            }
        }

        public float Volume
        {
            get
            {
                return volume;
            }

            set
            {
                volume = value;
                if (hasSourceId)
                {
                    // Volume
                    AL.Source(sourceId, ALSourcef.Gain, volume * 1.0f);
                }

            }
        }

        public SoundState State
        {
            get
            {
                return soundState;
            }
        }

        private float XnaPitchToAlPitch(float pitch)
        {
            // pitch is different in XNA and OpenAL. XNA has a pitch between -1 and 1 for one octave down/up.
            // openAL uses 0.5 to 2 for one octave down/up, while 1 is the default. The default value of 0 would make it completely silent.
            return (float)Math.Exp(0.69314718 * pitch);
        }

        public void Play()
        {
            if (!hasSourceId)
            {
                bufferIds = AL.GenBuffers(BUFFERCOUNT);
                sourceId = AL.GenSource();
                hasSourceId = true;
            }
            soundState = SoundState.Playing;

            if (bufferFillerThread == null)
            {
                bufferIdsToFill = bufferIds;
                currentBufferToFill = 0;
                OnBufferNeeded();
                bufferFillerThread = new Thread(BufferFiller);
                bufferFillerThread.Start();
            }

            AL.SourcePlay(sourceId);
        }

        //public void Apply3D(AudioListener listener, AudioEmitter emitter)
        //{
        //    Apply3D(new AudioListener[] { listener }, emitter);
        //}

        public void Pause()
        {
            if (hasSourceId)
            {
                AL.SourcePause(sourceId);
                soundState = SoundState.Paused;
            }
        }


        //public void Apply3D(AudioListener[] listeners, AudioEmitter emitter)
        //{
        //    // get AL's listener position
        //    float x, y, z;
        //    AL.GetListener(ALListener3f.Position, out x, out y, out z);
        //
        //    for (int i = 0; i < listeners.Length; i++)
        //    {
        //        AudioListener listener = listeners[i];
        //
        //        // get the emitter offset from origin
        //        Vector3 posOffset = emitter.Position - listener.Position;
        //        // set up orientation matrix
        //        Matrix orientation = Matrix.CreateWorld(Vector3.Zero, listener.Forward, listener.Up);
        //        // set up our final position and velocity according to orientation of listener
        //        Vector3 finalPos = new Vector3(x + posOffset.X, y + posOffset.Y, z + posOffset.Z);
        //        finalPos = Vector3.Transform(finalPos, orientation);
        //        Vector3 finalVel = emitter.Velocity;
        //        finalVel = Vector3.Transform(finalVel, orientation);
        //
        //        // set the position based on relative positon
        //        AL.Source(sourceId, ALSource3f.Position, finalPos.X, finalPos.Y, finalPos.Z);
        //        AL.Source(sourceId, ALSource3f.Velocity, finalVel.X, finalVel.Y, finalVel.Z);
        //    }
        //}


        public void Dispose()
        {
            if (!isDisposed)
            {
                Stop(true);
                AL.DeleteBuffers(bufferIds);
                AL.DeleteSource(sourceId);
                bufferIdsToFill = null;
                hasSourceId = false;
                isDisposed = true;
            }
        }

        public void Stop()
        {
            if (hasSourceId)
            {
                AL.SourceStop(sourceId);
                int pendingBuffers = PendingBufferCount;
                if (pendingBuffers > 0)
                    AL.SourceUnqueueBuffers(sourceId, PendingBufferCount);
                if (bufferFillerThread != null)
                {
                    while (!mThreadStarted)
                    {
                    }

                    mRunning = false;
                    bufferFillerThread.Join();
                }
                bufferFillerThread = null;
            }
            soundState = SoundState.Stopped;
        }

        public void Stop(bool immediate)
        {
            Stop();
        }

        public TimeSpan GetSampleDuration(int sizeInBytes)
        {
            throw new NotImplementedException();
        }

        public int GetSampleSizeInBytes(TimeSpan duration)
        {
            int size = (int)(duration.TotalMilliseconds * ((float)sampleRate / 1000.0f));
            return (size + (size & 1)) * 16;
        }

        public void SubmitBuffer(byte[] buffer)
        {
            this.SubmitBuffer(buffer, 0, buffer.Length);
        }

        public void SubmitBuffer(byte[] buffer, int offset, int count)
        {
            if (bufferIdsToFill != null)
            {
                AL.BufferData(bufferIdsToFill[currentBufferToFill], format, buffer, count, sampleRate);
                AL.SourceQueueBuffer(sourceId, bufferIdsToFill[currentBufferToFill]);
                currentBufferToFill++;
                if (currentBufferToFill >= bufferIdsToFill.Length)
                    bufferIdsToFill = null;
                else
                    OnBufferNeeded();
            }
            else
            {
                throw new Exception("Buffer already full.");
            }
        }

        private void BufferFiller()
        {
            mRunning = true;
            mThreadStarted = true;
            while (mRunning)
            {
                var state = AL.GetSourceState(sourceId);
                if (state == ALSourceState.Stopped || state == ALSourceState.Initial)
                    AL.SourcePlay(sourceId);

                if (bufferIdsToFill != null)
                {
                    continue;
                }

                int buffersProcessed;
                AL.GetSource(sourceId, ALGetSourcei.BuffersProcessed, out buffersProcessed);

                if (buffersProcessed == 0)
                {
                    Thread.Sleep(1); // Don't overload CPU.
                    continue;
                }

                bufferIdsToFill = AL.SourceUnqueueBuffers(sourceId, buffersProcessed);
                currentBufferToFill = 0;

                lock (SoundEngine3.SoundEngine.SoundLocker)
                {
                    OnBufferNeeded();
                }
            }
            mThreadStarted = false;
        }

        public bool IsLooped
        {
            get
            {
                return looped;
            }

            set
            {
                looped = value;
            }
        }

        public int PendingBufferCount
        {
            get
            {
                if (hasSourceId)
                {
                    int buffersQueued;
                    AL.GetSource(sourceId, ALGetSourcei.BuffersQueued, out buffersQueued);
                    return buffersQueued;
                }
                return 0;
            }
        }
    }
}
#endif

namespace ZquenceStudio3_SharedClasses.SoundEngine
{
    public class OpenALAdapter : CommonRenderer
    {
        private DynamicSoundEffectInstance mAudioTrack;
        private int mBufferSize;
        private int mSampleRate;
        private AudioChannels mChannels;
        private byte[] mBytesBuffer;
        
        public override int MinimumBufferSize
        {
            get { return mBufferSize; }
        }

        public override int MaximumBufferSize
        {
            get { return mBufferSize; }
        }

        public override int PreferredBufferSize
        {
            get { return mBufferSize; }
        }

        public override double SampleRate
        {
            get { return mSampleRate; }
        }

        public override int InputChannels
        {
            get { return 0; }
        }

        public override int OutputChannels
        {
            get { return mChannels == AudioChannels.Stereo ? 2 : 1; }
        }

        public override bool Started
        {
            get
            {
                return mAudioTrack.State == SoundState.Playing;
            }
        }

#if WindowsPhone
        private const string NAME = "XNA";
#else
        private const string NAME = "OpenAL";
#endif
		private Exception mInitError;

        public OpenALAdapter()
            : base(NAME)
        {
            mBufferSize = 1024 * 4;
            mChannels = AudioChannels.Stereo;
            mSampleRate = 44100;

			try
			{
				// Define all available sound devices.
				foreach (var device in OpenTK.Audio.AudioContext.AvailableDevices)
				{
					SoundDevice soundDevice = new SoundDevice(device, this, device);
					AddSoundDevice(soundDevice);
				}

#if !Android
                foreach (var device in OpenTK.Audio.AudioCapture.AvailableDevices)
				{
					AudioInput input = new AudioInput(this, null, device);
					AddInput(input);
				}
#endif
			}
			catch (Exception ex)
			{
				mInitError = ex;

                // Init default OpenAL device.
                SoundDevice soundDevice = new SoundDevice("Default test device", this, string.Empty);
                AddSoundDevice(soundDevice);
            }
        }

        public void Config(int inputChannels, int outputChannels, int bufferSize)
        {
            AudioBufferProcessor.Config(outputChannels, bufferSize);
        }

        protected override void SetBufferSize(int size)
        {
            if (mBufferSize != size)
            {
                AudioBufferProcessor.Stop();
                mAudioTrack.Stop();

                lock (SoundEngine3.SoundEngine.SoundLocker)
                {
                    mBufferSize = size;
                    SoundEngineAdapter_OnBufferSizeChanged(mBufferSize, mBufferSize, mBufferSize);
                    mBytesBuffer = new byte[(PreferredBufferSize + PreferredBufferSize) * OutputChannels]; // 2 bytes * channel.

                    AudioBufferProcessor.Config(OutputChannels, PreferredBufferSize);
                }

                AudioBufferProcessor.Start();
                mAudioTrack.Play();
            }
        }

        protected override void SetSampleRate(int sampleRate)
        {
            if (mSampleRate != sampleRate)
            {
                AudioBufferProcessor.Stop();
                mAudioTrack.Stop();
                mAudioTrack.Dispose();

                lock (SoundEngine3.SoundEngine.SoundLocker)
                {
                    mSampleRate = sampleRate;
                    SoundEngineAdapter_OnSampleRateChanged(mSampleRate);
                }

                AudioBufferProcessor.Start();

                mAudioTrack = new DynamicSoundEffectInstance(mSampleRate, mChannels);
                mAudioTrack.BufferNeeded += mAudioTrack_BufferNeeded;
                mAudioTrack.Play();
            }
        }
        
        public override void Initialize(int bufferSize, int sampleRate)
		{
            mBufferSize = bufferSize;
            mSampleRate = sampleRate;

            // Setup parameters.
            SoundEngineAdapter_OnInputChannelChanged (0);
			SoundEngineAdapter_OnOutputChannelChanged (2);
			SoundEngineAdapter_OnSampleRateChanged (mSampleRate);
			SoundEngineAdapter_OnBufferSizeChanged (mBufferSize, mBufferSize, mBufferSize);

			mBytesBuffer = new byte[(PreferredBufferSize + PreferredBufferSize) * OutputChannels]; // 2 bytes * channel.

			// Setup audio.
			mAudioTrack = new DynamicSoundEffectInstance (mSampleRate, mChannels);
			mAudioTrack.BufferNeeded += mAudioTrack_BufferNeeded;

            Config(InputChannels, OutputChannels, PreferredBufferSize);
        }

        void SoundEngineAdapter_OnSampleRateChanged(double sampleRate)
        {
            AudioInput.SampleRate = sampleRate;
            AudioOutput.SampleRate = sampleRate;

            RaiseOnSampleRateChanged(sampleRate);
        }

        void SoundEngineAdapter_OnBufferSizeChanged(int min, int max, int preferred)
        {
            foreach (var channel in AudioInput.Channels)
            {
                channel.SetBufferSize(preferred);
            }
            foreach (var channel in AudioOutput.Channels)
            {
                channel.SetBufferSize(preferred);
            }

            RaiseOnBufferSizeChanged(min, max, preferred);
        }

        // TODO: Optimera minnet.
        void SoundEngineAdapter_OnInputChannelChanged(int channels)
        {
            AudioInput.SetChannels(channels, PreferredBufferSize);

            RaiseOnInputChannelChanged(channels);
        }

        // TODO: Optimera minnet.
        void SoundEngineAdapter_OnOutputChannelChanged(int channels)
        {
            AudioOutput.SetChannels(channels, PreferredBufferSize);

            RaiseOnOutputChannelChanged(channels);
        }

        void mAudioTrack_BufferNeeded()
        {
            // Clear audio.
            foreach (var channel in AudioOutput.Channels)
            {
                Array.Clear(channel.Samples, 0, channel.Samples.Length);
            }

            // Trigger the on buffer event.
            //RaiseOnBufferUpdate(AudioInput, AudioOutput);
            AudioBufferProcessor.GetBuffer(AudioOutput);

            // Submit.
            Submit(AudioOutput.Channels[0].Samples, AudioOutput.Channels[1].Samples);
        }

        public void Submit(double[] left, double[] right)
        {
            int j = mBytesBuffer.Length;
            uint left_length = (uint)left.Length;

            // Mono
            if (right == null)
            {
                for (uint i = left_length - 1; i != 4294967295; --i)
                {
                    left[i] *= short.MaxValue;
                    right[i] *= short.MaxValue;

                    if (left[i] > short.MaxValue) left[i] = short.MaxValue;
                    else if (left[i] < short.MinValue) left[i] = short.MinValue;

                    int sLeft = (int)left[i];

#if (XBOX360)
                    buffer[--j] = (byte)sLeft;
                    buffer[--j] = (byte)(sLeft >> 8);
#else
                    mBytesBuffer[--j] = (byte)(sLeft >> 8);
                    mBytesBuffer[--j] = (byte)sLeft;
#endif
                }
            }
            // Stereo
            else
            {
                for (uint i = left_length - 1; i != 4294967295; --i)
                {
                    left[i] *= short.MaxValue;
                    right[i] *= short.MaxValue;

                    if (left[i] > short.MaxValue) left[i] = short.MaxValue;
                    else if (left[i] < short.MinValue) left[i] = short.MinValue;

                    if (right[i] > short.MaxValue) right[i] = short.MaxValue;
                    else if (right[i] < short.MinValue) right[i] = short.MinValue;

                    int sLeft = (int)left[i];
                    int sRight = (int)right[i];

#if (XBOX360)
                    buffer[--j] = (byte)sLeft;
                    buffer[--j] = (byte)(sLeft >> 8);
                    buffer[--j] = (byte)sRight;
                    buffer[--j] = (byte)(sRight >> 8);
                    
#else
                    mBytesBuffer[--j] = (byte)(sLeft >> 8);
                    mBytesBuffer[--j] = (byte)sLeft;
                    mBytesBuffer[--j] = (byte)(sRight >> 8);
                    mBytesBuffer[--j] = (byte)sRight;
#endif

                }
            }

            // Submit to the speaker.
            mAudioTrack.SubmitBuffer(mBytesBuffer, 0, mBytesBuffer.Length);
        }

        public override void SetSoundDevice(SoundEngine3.SoundDevice soundDevice)
        {
            if (soundDevice == null)
            {
                try
                {
                    // Close sound device.
                    Alc.CloseDevice(Alc.GetCurrentContext().Handle);
                }
                catch (Exception error)
                {
                }
            }
            else
            {
                ContextHandle context = ContextHandle.Zero;
				IntPtr device = Alc.OpenDevice((string)soundDevice.DeviceIdentifier);

                if (device != IntPtr.Zero)
                {
                    int[] attributes = new int[0];
                    context = Alc.CreateContext(device, attributes);

                    if (context != ContextHandle.Zero)
                    {
                        Alc.MakeContextCurrent(context);
                    }
                }
            }
        }

        public override void Start()
        {
            AudioBufferProcessor.Start();
            mAudioTrack.Play();
        }

        public override void Stop()
        {
            // Note: It can be null if an thrown has been called due the initialize time.
            if (mAudioTrack != null)
            {
                AudioBufferProcessor.Stop();
                mAudioTrack.Stop();
            }
        }

        public override void Dispose()
        {
            // Note: It can be null if an thrown has been called due the initialize time.
            if (mAudioTrack != null)
            {
                mAudioTrack.Dispose();
            }
        }
        
        protected override void OnRequestUpdateParameters(out int sampleRate, out BufferSizes bufferSize)
        {
            sampleRate = (int)mSampleRate;
            bufferSize = (BufferSizes)mBufferSize;
        }

        protected override void FeatchInput()
        {

        }
    }
}